#include "data-tracker.h"

namespace data_tracker
{
    String logRootFolderName = "DataTracker";
    String startCPUTickFileName = "start_cpu_tick.txt";
    UInt maxWriteFileTaskCount = 65536;

    Path ensureRootFolder()
    {
        Path rootFolder = Path::homePath().childFolder("Desktop").childFolder(logRootFolderName);
        if (!rootFolder.exist()) rootFolder.folderMake();
        if (!rootFolder.exist()) return Path();

        return rootFolder;
    }

    Path ensureFrameFolder(String frameID)
    {
        Path rootFolder = ensureRootFolder();
        if (rootFolder.isNull()) return Path();

        Path frameFolder = rootFolder.childFolder(frameID);
        if (!frameFolder.exist()) frameFolder.folderMake();
        if (!frameFolder.exist()) return Path();

        return frameFolder;
    }

    struct WriteFileTask
    {
        Bool valid;
        String frameID;
        String location;
        ULong cpuTick;
        WriteFileTask() : valid(FALSE), cpuTick(0)
        {}
        WriteFileTask(String frameID, String location, ULong cpuTick) : valid(TRUE), frameID(frameID), location(location), cpuTick(cpuTick)
        {}
    };

    class DataTrackerThreadVars : public Vars
    {
    public:
        Stream<WriteFileTask> writeFileTaskStream;
        DataTrackerThreadVars() : writeFileTaskStream(maxWriteFileTaskCount)
        {}
    };

    class DataTrackerThread : public Object<DataTrackerThreadVars>, public IWorkflow
    {
    public:
        DataTrackerThread() : Object<DataTrackerThreadVars>(new DataTrackerThreadVars(), TRUE)
        {}
        void add(WriteFileTask task)
        {
            vars->writeFileTaskStream.enqueue(task);
        }
    private:
        Array<String> getThreadNames() override
        {
            return Array<String>::scalar("data_tracker_thread");
        }
        void onThreadLoop(UInt threadIndex, Flag shouldEnd) override
        {
            Array<WriteFileTask> tasks = vars->writeFileTaskStream.dequeue(1);
            if (tasks.isEmpty()) return;
            
            WriteFileTask task = tasks[0];
            if (!task.valid) return;

            Path rootFolder = ensureRootFolder();
            Path frameFolder = ensureFrameFolder(task.frameID);
            if (frameFolder.isNull()) return;

            Path globalStartCPUTickPath = rootFolder.childFile(startCPUTickFileName);
            Path localStartCPUTickPath = frameFolder.childFile(startCPUTickFileName);
            Path targetStartCPUTickPath;
            if (localStartCPUTickPath.exist()) targetStartCPUTickPath = localStartCPUTickPath;
            else if (globalStartCPUTickPath.exist()) targetStartCPUTickPath = globalStartCPUTickPath;

            ULong startCPUTick = 0;
            if (targetStartCPUTickPath.isValid())
            {
                auto longValue = File::openText(targetStartCPUTickPath).scan().toLong();
                if (longValue.isValid() && longValue.value() > 0) startCPUTick = (ULong)longValue.value();
            }

            String fileName;
            if (startCPUTick != 0)
            {
                Double timeOffset = (Double)(task.cpuTick - startCPUTick) / Timer::cpuTicksPerSecond();
                UInt timeOffsetSecond = (UInt)math::floor(timeOffset);
                UInt timeOffsetUS = (UInt)math::floor((timeOffset - timeOffsetSecond) * 1000000);
                fileName = String(timeOffsetSecond, 5) + "_" + String(timeOffsetUS, 6) + "_" + task.location;
            }
            else
            {
                fileName = String(task.cpuTick) + "_" + task.location;
            }
            frameFolder.childFile(fileName).fileCreate(0);
        }
    };

    DataTrackerThread threadObj;
    Threads threadContainer;
    Bool enabled = FALSE;
    Lock containerLock;

    void enableDataTrackerThread()
    {
        containerLock.enter();
        if (!enabled)
        {
            enabled = TRUE;
            threadContainer = Threads::start(threadObj);
        }
        containerLock.leave();
    }
}

using namespace data_tracker;

void DataTracker::setStartCPUTick(ULong startCPUTick)
{
    ULong cpuTick = Timer::cpuTick();
    if (startCPUTick > cpuTick) return;
    
    Path rootFolder = ensureRootFolder();
    if (rootFolder.isNull()) return;

    for (auto e = rootFolder.folderContents().firstElem(); e.valid(); ++e)
    {
        e->remove();
    }

    File::createText(rootFolder.childFile(startCPUTickFileName)).print(startCPUTick);
}

void DataTracker::mark(String frameID, String location)
{
    ULong cpuTick = Timer::cpuTick();
    enableDataTrackerThread();
    threadObj.add(WriteFileTask(frameID, location, cpuTick));
}